#include "userprog/process.h"
#include <debug.h>
#include <inttypes.h>
#include <round.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "userprog/gdt.h"
#include "userprog/pagedir.h"
#include "userprog/tss.h"
#include "filesys/directory.h"
#include "filesys/file.h"
#include "filesys/filesys.h"
#include "threads/flags.h"
#include "threads/init.h"
#include "threads/interrupt.h"
#include "threads/palloc.h"
#include "threads/thread.h"
#include "threads/vaddr.h"

static thread_func start_process NO_RETURN;
static bool load(const char *cmdline, void (**eip)(void), void **esp);

tid_t process_execute(const char *cmd_line) {
    char *fn_copy;
    tid_t tid;

    /* Allocate and initialize child status structure. */
    struct child_status *child = malloc(sizeof(struct child_status));
    if (child == NULL)
        return TID_ERROR;

    sema_init(&child->sema, 0);
    child->exited = false;
    child->exit_status = -1;

    /* Make a copy of CMD_LINE. */
    fn_copy = palloc_get_page(0);
    if (fn_copy == NULL) {
        free(child);
        return TID_ERROR;
    }
    strlcpy(fn_copy, cmd_line, PGSIZE);

    /* Create a new thread to execute the program. */
    tid = thread_create(cmd_line, PRI_DEFAULT, start_process, fn_copy);
    if (tid == TID_ERROR) {
        free(child);
        palloc_free_page(fn_copy);
    } else {
        child->tid = tid;
        list_push_back(&thread_current()->children, &child->elem);
    }

    return tid;
}

static void start_process(void *cmd_line_) {
    char *cmd_line = cmd_line_;
    struct intr_frame if_;
    bool success;

    memset(&if_, 0, sizeof if_);
    if_.gs = if_.fs = if_.es = if_.ds = if_.ss = SEL_UDSEG;
    if_.cs = SEL_UCSEG;
    if_.eflags = FLAG_IF | FLAG_MBS;
    success = load(cmd_line, &if_.eip, &if_.esp);

    if (!success) {
        thread_exit();
    }
    asm volatile("movl %0, %%esp; jmp intr_exit" : : "g"(&if_) : "memory");
}

int process_wait(tid_t child_tid) {
    struct thread *cur = thread_current();
    struct list_elem *e;

    for (e = list_begin(&cur->children); e != list_end(&cur->children); e = list_next(e)) {
        struct child_status *child = list_entry(e, struct child_status, elem);
        if (child->tid == child_tid) {
            if (!child->exited) {
                sema_down(&child->sema);
            }

            int status = child->exit_status;
            list_remove(&child->elem);
            free(child);
            return status;
        }
    }
    return -1; // Child not found.
}

void process_exit(void) {
    struct thread *cur = thread_current();
    struct list_elem *e;

    for (e = list_begin(&cur->parent->children); e != list_end(&cur->parent->children); e = list_next(e)) {
        struct child_status *child = list_entry(e, struct child_status, elem);
        if (child->tid == cur->tid) {
            child->exit_status = cur->exit_status;
            child->exited = true;
            sema_up(&child->sema);
            break;
        }
    }

    uint32_t *pd = cur->pagedir;
    if (pd != NULL) {
        cur->pagedir = NULL;
        pagedir_activate(NULL);
        pagedir_destroy(pd);
    }
}